/**
 * 
 */
package com.dalonedrow.engine.systems;

import java.nio.FloatBuffer;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.GL11;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.BufferUtils;
import com.dalonedrow.engine.sprite.base.SimplePoint;
import com.dalonedrow.engine.sprite.base.SpriteBase;
import com.dalonedrow.engine.sprite.base.SpriteComparator;
import com.dalonedrow.engine.sprite.impl.GroupOfImages;
import com.dalonedrow.engine.sprite.impl.GroupOfLines;
import com.dalonedrow.engine.sprite.impl.GroupOfText;
import com.dalonedrow.engine.sprite.impl.SpriteTileMap;
import com.dalonedrow.globalvariables.constants.hq.GlobalConstants;
import com.dalonedrow.module.hq.DawnQuestGame;
import com.dalonedrow.utils.TextureLoader;

/**
 * @author Donald
 */
public class Render {
	/**
	 * the length in bytes of a five-byte stride - 2 bytes for position, 2 bytes
	 * for texture coordinates, 1 byte for color.
	 */
	private static final int	FIVE_BYTE_STRIDE	= 5 * GlobalConstants.BYTES_PER_FLOAT;
	/** the number of indices required to render a quad. */
	private static final int	INDICES_PER_QUAD	= 6;
	/** the one and only instance of the <code>Render</code> class. */
	private static Render		instance;
	/** the number of vertices required to render a quad. */
	private static final int	VERTICES_PER_QUAD	= 4;
	/**
	 * Render Gives access to the singleton instance of {@link Render}.
	 * @return {@link Render}
	 */
	public static Render getInstance() {
		if (Render.instance == null) {
			Render.instance = new Render();
		}
		return Render.instance;
	}
	/** the buffer's current capacity. */
	private int					currentCapacity;
	/** the list of {@link SpriteBase}s to be rendered. */
	private List<SpriteBase>	currentSpriteIteration;
	private boolean				debug				= false;
	/**
	 * the length in bytes of a six-byte stride - 2 bytes for position, 2 bytes
	 * for texture coordinates, 4 bytes for color.
	 */
	private final int			EIGHT_BYTE_STRIDE	= 8 * GlobalConstants.BYTES_PER_FLOAT;
	/** the data buffer used for the IBO. */
	private ShortBuffer			indexData;
	/** the list of {@link SpriteBase}s to be rendered. */
	private List<SpriteBase>	list;
	/**
	 * the length in bytes of a six-byte stride - 2 bytes for position, 4 bytes
	 * for color.
	 */
	private final int			SIX_BYTE_STRIDE		= 6 * GlobalConstants.BYTES_PER_FLOAT;
	/**
	 * the length in bytes of a three-byte stride - 2 bytes for position, 1 byte
	 * for color.
	 */
	private final int			THREE_BYTE_STRIDE	= 3 * GlobalConstants.BYTES_PER_FLOAT;
	/** the number of values per color. */
	private final int			VALUES_PER_COLOR	= 4;
	/** the data buffer used for the VBO. */
	private FloatBuffer			vertexData;
	/** Hidden constructor. */
	private Render() {
		final int capacity = 1000;
		final int allocate = 1024;
		currentSpriteIteration = new ArrayList<SpriteBase>(capacity);
		vertexData = BufferUtils.newFloatBuffer(allocate);
		indexData = BufferUtils.newShortBuffer(allocate);
		list = new ArrayList<SpriteBase>(capacity);
	}
	/**
	 * Registers an entity to be rendered during the next cycle.
	 * @param sprite the {@link SpriteBase} entity
	 */
	public void registerEntity(final SpriteBase sprite) {
		list.add(sprite);
	}
	/**
	 * Render all sprites and images.
	 * @param gl the {@link GL2} context
	 * @throws Exception if an error occurs
	 */
	public void render() throws Exception {
		if (debug) {
			System.out.println("************BEGIN RENDER");
		}
		Collections.sort(list, new SpriteComparator());
		if (Gdx.graphics.isGL11Available()
				|| Gdx.graphics.isGL20Available()) {
			renderVBO();
		} else {
			renderImmediateMode();
		}
		// empty the list after rendering
		list.clear();
		if (debug) {
			System.out.println("************END RENDER");
		}
	}
	private void renderImmediateMode() throws Exception {
		/*
		 * boolean begunQuads = false; int textureId = -999; Texture texture =
		 * null; for (int index = 0; index < list.size(); index++) { SpriteBase
		 * sprite = list.get(index); if (sprite.getPosition() != null) { switch
		 * (sprite.getType()) { case SpriteBase.TYPE_IMAGE: SpriteImage img =
		 * sprite.getImage(); // switch textures if needed if
		 * (img.getTextureId() != textureId) { if (texture != null) {
		 * texture.disable(gl); } texture =
		 * GameController.getInstance().getTexture( img.getTextureId());
		 * texture.enable(gl); texture.bind(gl); } Vector3 v =
		 * sprite.getPosition(); gl.glColor4fv(GlobalMethods.colorToBuffer(
		 * sprite.getColor())); if (!begunQuads) { gl.glBegin(GL2.GL_QUADS);
		 * begunQuads = true; } gl.glTexCoord2d(img.getLeft(), img.getTop());
		 * gl.glVertex3f( (float) v.getX(), (float) (v.getY() +
		 * sprite.getHeight()), 0f); // start at left-top corner
		 * gl.glTexCoord2d(img.getRight(), img.getTop()); gl.glVertex3f( (float)
		 * (v.getX() + sprite.getWidth()), (float) (v.getY() +
		 * sprite.getHeight()), 0f); // right-top
		 * gl.getGL2().glTexCoord2d(img.getRight(), img.getBottom());
		 * gl.glVertex3f( (float) (v.getX() + sprite.getWidth()), (float)
		 * v.getY(), 0f); // right-bottom
		 * gl.getGL2().glTexCoord2d(img.getLeft(), img.getBottom());
		 * gl.glVertex3f( (float) v.getX(), (float) v.getY(), 0f); //
		 * left-bottom // free up resources for garbage collection img = null; v
		 * = null; break; default: textureId = -999; if (texture != null) {
		 * texture.disable(gl); } texture = null; // rendering a primitive //
		 * change color gl.glColor4fv(GlobalMethods.colorToBuffer(
		 * sprite.getColor())); // begin primitive if (begunQuads) { if
		 * (sprite.getType() != GL2.GL_QUADS) { gl.getGL2().glEnd();
		 * gl.glBegin(sprite.getType()); begunQuads = false; } } else { /*
		 * gl.glBegin(sprite.getType().getGlPrimitive()); if
		 * (sprite.getType().getGlPrimitive() == GL2.GL_QUADS) { begunQuads =
		 * true; }
		 *//*
			 * } float[] pts = sprite.getCoordinates(); for (int i = 0; i <
			 * pts.length; i += 2) { float x = pts[i], y = pts[i + 1];
			 * gl.glVertex2f(x, y); } pts = null; if (!begunQuads) {
			 * gl.getGL2().glEnd(); } break; } } // free up resources for
			 * garbage collection sprite = null; } if (begunQuads) {
			 * gl.getGL2().glEnd(); begunQuads = false; } // after rendering,
			 * disable the last texture if (texture != null) {
			 * texture.disable(gl); } // free up resources for garbage
			 * collection textureId = -999; texture = null;
			 */
	}
	/**
	 * Renders non-textured line vertices using a vertex buffer object.
	 * @param gl2 the {@link GL2} context
	 * @param vertexData the vertex data
	 * @param numVertices the number of vertices rendered
	 */
	private void renderLineVertices(final int numVertices) {
		GL11 gl = Gdx.graphics.getGL11();

		int vbo = DawnQuestGame.getInstance().getVBO();
		// set the current VBO with this call. the 2nd parameter is the
		// id of the VBO created.
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo);

		// get the size of the data
		int numBytes = currentCapacity * GlobalConstants.BYTES_PER_FLOAT;
		// data is stored in the VBO with this call
		gl.glBufferData(
				GL11.GL_ARRAY_BUFFER, // should match the first parameter
				// used when calling glBindBuffer
				numBytes, // the size of the data in bytes
				vertexData, // the buffer data
				GL11.GL_DYNAMIC_DRAW); // a "hint" to openGL on how the
		// data will be used. possible
		// values are:
		// GL.GL_STATIC_DRAW - the data will
		// be used many times and never
		// change, so it can be stored on
		// the graphics card
		// GL2.GL_STREAM_DRAW - the data will
		// be used once or a few times, so
		// it's not important to save it on
		// the graphics card
		// GL.GL_DYNAMIC_DRAW - the data
		// will be changed many times and
		// should be stored in memory that
		// can be quickly access by the CPU
		// and the graphics card.
		// animations and shifting vertex
		// data that can change from one
		// frame to the next
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);

		// render the data now
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo);
		// enable the use of the array
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// tell OpenGL the vertices are coming from the buffer
		gl.glVertexPointer(
				2, // the number of coordinates per vertex
				GL10.GL_FLOAT, // type of numbers used
				THREE_BYTE_STRIDE, // the distance in bytes
				// between vertices
				0 // the starting position of the data
		// within the VBO
		);
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glColorPointer(
				VALUES_PER_COLOR, // the buffer values per color
				GL10.GL_UNSIGNED_BYTE, // type of numbers used
				THREE_BYTE_STRIDE, // the stride per values is 6 bytes
				2 * GlobalConstants.BYTES_PER_FLOAT // 2 byte offset to grab
													// color values
		);
		// draw the array
		gl.glDrawArrays(
				GL10.GL_LINES, // the primitive type
				0, // the first index drawn
				numVertices // the number of vertices per shape
		);
		// disable arrays
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
	}
	/**
	 * Renders non-textured quad vertices using a vertex buffer object.
	 * @param gl2 the {@link GL2} context
	 * @param vertexData the vertex data
	 * @param numVertices the number of vertices rendered
	 */
	private void renderQuadVertices(final int numVertices) {
		GL11 gl = Gdx.graphics.getGL11();
		int vbo = DawnQuestGame.getInstance().getVBO();
		int ibo = DawnQuestGame.getInstance().getIBO();
		int numIndices = numVertices / 4 * Render.INDICES_PER_QUAD;
		// bind vbo
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo);
		// bind ibo
		gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, ibo);
		// enable vertex arrays
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// tell OpenGL the vertices are coming from the buffer
		gl.glVertexPointer(
				2, // the number of coordinates per vertex
				GL10.GL_FLOAT, // the data type of each coordinate in the array
				THREE_BYTE_STRIDE, // the distance in bytes between vertices
				0 // the starting position of the data within the VBO
		);
		// enable color arrays
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glColorPointer(
				4, // the buffer values per color
				GL10.GL_UNSIGNED_BYTE, // type of numbers used
				THREE_BYTE_STRIDE, // the stride per values is 6 bytes
				2 * GlobalConstants.BYTES_PER_FLOAT // 2 byte offset to grab
													// color values
		);
		// store the data in the IBO
		// clear buffer by initial pointer to null
		gl.glBufferData(
				GL11.GL_ELEMENT_ARRAY_BUFFER,
				numIndices * GlobalConstants.BYTES_PER_SHORT,
				null,
				GL11.GL_DYNAMIC_DRAW
				);
		// store index data
		gl.glBufferData(
				GL11.GL_ELEMENT_ARRAY_BUFFER, // the target buffer
				numIndices * GlobalConstants.BYTES_PER_SHORT, // the size in
				// bytes of the data
				indexData, // the data
				GL11.GL_DYNAMIC_DRAW // a "hint" to openGL on how the
		// data will be used
		);
		// store the data in the VBO
		// clear buffer by initial pointer to null
		gl.glBufferData(
				GL11.GL_ARRAY_BUFFER,
				numVertices * GlobalConstants.BYTES_PER_FLOAT,
				null,
				GL11.GL_DYNAMIC_DRAW);
		gl.glBufferData(
				GL11.GL_ARRAY_BUFFER, // the target buffer
				numVertices * GlobalConstants.BYTES_PER_FLOAT, // the size in
				// bytes of the data
				vertexData, // the data
				GL11.GL_DYNAMIC_DRAW// a "hint" to openGL on how the
		// data will be used
		);
		// draw the indices
		gl.glDrawElements(GL10.GL_TRIANGLES, // the rendering mode
				numIndices, // the number of elements to render
				GL10.GL_UNSIGNED_SHORT, // the type of the values in indices
				0 // a pointer to the location where the indices are stored
		);
		// unbind the buffers
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
		gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
		// disable arrays
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
	}
	/**
	 * Renders textured quad vertices using a vertex buffer object.
	 * @param textureId the if the texture rendered from
	 * @param numVertices the number of vertices rendered
	 * @throws Exception if an error occurs
	 */
	private void renderTexturedQuadVertices(final int textureId,
			final int numVertices) throws Exception {
		GL11 gl = Gdx.graphics.getGL11();
		int vbo = DawnQuestGame.getInstance().getVBO();
		int ibo = DawnQuestGame.getInstance().getIBO();
		int numIndices = numVertices / 4 * Render.INDICES_PER_QUAD;
		// bind vbo
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo);
		// bind ibo
		gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, ibo);
		// enable vertex arrays
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// tell OpenGL the vertices are coming from the buffer
		gl.glVertexPointer(
				2, // the number of coordinates per vertex
				GL10.GL_FLOAT, // the data type of each coordinate in the array
				Render.FIVE_BYTE_STRIDE, // the distance in bytes between
											// vertices
				0 // the starting position of the data within the VBO
		);
		// enable texture arrays
		gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glTexCoordPointer(
				2, // the number of coordinates per array element
				GL10.GL_FLOAT, // type of numbers used
				Render.FIVE_BYTE_STRIDE, // the distance in bytes
				// between coordinate sets
				2 * GlobalConstants.BYTES_PER_FLOAT);
		// enable color arrays
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glColorPointer(
				4, // the buffer values per color
				GL10.GL_UNSIGNED_BYTE, // type of numbers used
				Render.FIVE_BYTE_STRIDE, // the stride per values is 5 bytes
				4 * GlobalConstants.BYTES_PER_FLOAT // 4 byte offset to grab
													// color values
		);
		// store the data in the IBO
		// clear buffer by initial pointer to null
		gl.glBufferData(
				GL11.GL_ELEMENT_ARRAY_BUFFER,
				numIndices * GlobalConstants.BYTES_PER_SHORT,
				null,
				GL11.GL_DYNAMIC_DRAW
				);
		// store index data
		gl.glBufferData(
				GL11.GL_ELEMENT_ARRAY_BUFFER, // the target buffer
				numIndices * GlobalConstants.BYTES_PER_SHORT, // the size in
				// bytes of the data
				indexData, // the data
				GL11.GL_DYNAMIC_DRAW // a "hint" to openGL on how the
		// data will be used
		);
		// store the data in the VBO
		// clear buffer by initial pointer to null
		gl.glBufferData(
				GL11.GL_ARRAY_BUFFER,
				numVertices * GlobalConstants.BYTES_PER_FLOAT,
				null,
				GL11.GL_DYNAMIC_DRAW);
		gl.glBufferData(
				GL11.GL_ARRAY_BUFFER, // the target buffer
				numVertices * GlobalConstants.BYTES_PER_FLOAT, // the size in
				// bytes of the data
				vertexData, // the data
				GL11.GL_DYNAMIC_DRAW// a "hint" to openGL on how the
		// data will be used
		);
		// bind the texture
		Texture texture = TextureLoader.getInstance().getTexture(textureId);
		// texture.enable(gl2);
		gl.glEnable(GL10.GL_TEXTURE_2D);
		texture.bind();
		// draw the indices
		gl.glDrawElements(GL10.GL_TRIANGLES, // the rendering mode
				numIndices, // the number of elements to render
				GL10.GL_UNSIGNED_SHORT, // the type of the values in indices
				0 // a pointer to the location where the indices are stored
		);
		gl.glDisable(GL10.GL_TEXTURE_2D);

		texture = null;
		// unbind the buffers
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);
		gl.glBindBuffer(GL11.GL_ELEMENT_ARRAY_BUFFER, 0);
		// disable arrays
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
		gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
	}
	/**
	 * Renders non-textured triangle vertices using a vertex buffer object.
	 * @param numVertices the number of vertices rendered
	 */
	private void renderTriangleVertices(final int numVertices) {
		GL11 gl = Gdx.graphics.getGL11();

		int vbo = DawnQuestGame.getInstance().getVBO();
		// set the current VBO with this call. the 2nd parameter is the
		// id of the VBO created.
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo);

		// get the size of the data
		int numBytes = currentCapacity * GlobalConstants.BYTES_PER_FLOAT;
		// data is stored in the VBO with this call
		gl.glBufferData(
				GL11.GL_ARRAY_BUFFER, // should match the first parameter
				// used when calling glBindBuffer
				numBytes, // the size of the data in bytes
				vertexData, // the buffer data
				GL11.GL_DYNAMIC_DRAW); // a "hint" to openGL on how the
		// data will be used. possible
		// values are:
		// GL.GL_STATIC_DRAW - the data will
		// be used many times and never
		// change, so it can be stored on
		// the graphics card
		// GL2.GL_STREAM_DRAW - the data will
		// be used once or a few times, so
		// it's not important to save it on
		// the graphics card
		// GL.GL_DYNAMIC_DRAW - the data
		// will be changed many times and
		// should be stored in memory that
		// can be quickly access by the CPU
		// and the graphics card.
		// animations and shifting vertex
		// data that can change from one
		// frame to the next
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, 0);

		// render the data now
		gl.glBindBuffer(GL11.GL_ARRAY_BUFFER, vbo);
		// enable the use of the array
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// tell OpenGL the vertices are coming from the buffer
		gl.glVertexPointer(
				2, // the number of coordinates per vertex
				GL10.GL_FLOAT, // type of numbers used
				THREE_BYTE_STRIDE, // the distance in bytes
				// between vertices
				0 // the starting position of the data
		// within the VBO
		);
		gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		gl.glColorPointer(
				VALUES_PER_COLOR, // the buffer values per color
				GL10.GL_UNSIGNED_BYTE, // type of numbers used
				THREE_BYTE_STRIDE, // the stride per values is 6 bytes
				2 * GlobalConstants.BYTES_PER_FLOAT // 2 byte offset to grab
													// color values
		);
		// draw the array
		gl.glDrawArrays(
				GL10.GL_TRIANGLES, // the primitive type
				0, // the first index drawn
				numVertices // the number of vertices per shape
		);
		// disable arrays
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
		gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
	}
	private void renderVBO() throws Exception {
		final int noType = 999;
		int textureId = -noType;
		int indexTextureId = -noType;
		int currentType = noType, sprites = 0;
		currentCapacity = 0;
		currentSpriteIteration.clear();
		for (int index = 0; index < list.size(); index++) {
			if (list.get(index) == null) {
				continue;
			}
			if (currentType == noType) {
				currentType = list.get(index).getType();
			}
			if (list.get(index).getType() == SpriteBase.TYPE_00_BASIC_IMAGE
					|| list.get(index).getType()
						== SpriteBase.TYPE_01_GROUP_IMAGE
					|| list.get(index).getType()
						== SpriteBase.TYPE_03_GROUP_TEXT
					|| list.get(index).getType() == SpriteBase.TYPE_09_TILE_MAP
					|| list.get(index).getType() == SpriteBase.TYPE_10_PARTICLE)
			{
				indexTextureId =
						Sprite.getInstance().getTextureId(list.get(index));
			} else {
				// no texture needed
				indexTextureId = -noType;
			}
			if (textureId == -noType) {
				textureId = indexTextureId;
			}
			// what type of sprite is this?
			if (list.get(index).getType() == currentType
					&& currentType != SpriteBase.TYPE_00_BASIC_IMAGE
					&& currentType != SpriteBase.TYPE_01_GROUP_IMAGE
					&& currentType != SpriteBase.TYPE_03_GROUP_TEXT
					&& currentType != SpriteBase.TYPE_09_TILE_MAP
					&& currentType != SpriteBase.TYPE_10_PARTICLE) {
				// dealing with non-texture
				// adding more of same type
				if (currentType == SpriteBase.TYPE_08_TRIANGLES) {
					sprites++;
					currentCapacity += Sprite.TRI_SPRITE_LEN; // 9 floats for a
					// triangle primitive
				} else if (currentType == SpriteBase.TYPE_06_QUADS) {
					sprites++;
					currentCapacity += Sprite.QUAD_SPRITE_LEN; // 12 floats for
																// a
					// quad primitive
				} else if (currentType == SpriteBase.TYPE_04_LINE) {
					sprites++;
					currentCapacity += Sprite.LINE_SPRITE_LEN; // 12 floats for
																// a
					// line primitive
				} else if (currentType == SpriteBase.TYPE_02_GROUP_LINES) {
					sprites +=
							((GroupOfLines) list.get(index))
									.getNumberOfImages();
					currentCapacity += Sprite.LINE_SPRITE_LEN
							* ((GroupOfLines) list.get(index))
									.getNumberOfImages();
				}
				currentSpriteIteration.add(list.get(index));
			} else if (list.get(index).getType() == currentType
					&& (currentType == SpriteBase.TYPE_00_BASIC_IMAGE
							|| currentType == SpriteBase.TYPE_01_GROUP_IMAGE
							|| currentType == SpriteBase.TYPE_03_GROUP_TEXT
							|| currentType == SpriteBase.TYPE_09_TILE_MAP
							|| currentType == SpriteBase.TYPE_10_PARTICLE)
					&& indexTextureId == textureId) {
				// dealing with image from same texture
				// adding more of same type and texture
				if (currentType == SpriteBase.TYPE_00_BASIC_IMAGE
						|| currentType == SpriteBase.TYPE_10_PARTICLE) {
					sprites++;
					// 32 floats for a textured quad
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN;
				} else if (currentType == SpriteBase.TYPE_01_GROUP_IMAGE) {
					sprites +=
							((GroupOfImages) list.get(index))
									.getNumberOfImages();
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN
							* ((GroupOfImages) list.get(index))
									.getNumberOfImages();
				} else if (currentType == SpriteBase.TYPE_03_GROUP_TEXT) {
					if (list.get(index).isDebug()) {
						System.out.println("rendering line");
					}
					sprites +=
							((GroupOfText) list.get(index)).getNumCharacters();
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN
							* ((GroupOfText) list.get(index))
									.getNumCharacters();
				} else if (currentType == SpriteBase.TYPE_09_TILE_MAP) {
					int num =
							((SpriteTileMap) list.get(index))
									.getNumberOfImagesVisible();
					sprites += num;
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN * num;
				}
				currentSpriteIteration.add(list.get(index));
			} else {
				// changing type or texture
				// render the last list
				if (currentSpriteIteration.size() > 0) {
					renderVBOData(currentType, sprites, textureId);
				}

				// reset data
				currentType = list.get(index).getType();
				currentSpriteIteration.clear();
				currentCapacity = 0;
				sprites = 0;
				textureId = indexTextureId;
				// start a new list
				// increments the number of sprites in the list
				// and the current list capacity
				switch (currentType) {
				case SpriteBase.TYPE_00_BASIC_IMAGE:
				case SpriteBase.TYPE_10_PARTICLE:
					sprites++;
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN; // 32
																		// floats
																		// for a
					// textured quad
					break;
				case SpriteBase.TYPE_01_GROUP_IMAGE:
					sprites +=
							((GroupOfImages) list.get(index))
									.getNumberOfImages();
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN
							* ((GroupOfImages) list.get(index))
									.getNumberOfImages();
					break;
				case SpriteBase.TYPE_02_GROUP_LINES:
					sprites +=
							((GroupOfLines) list.get(index))
									.getNumberOfImages();
					currentCapacity += Sprite.LINE_SPRITE_LEN
							* ((GroupOfLines) list.get(index))
									.getNumberOfImages();
					break;
				case SpriteBase.TYPE_03_GROUP_TEXT:
					sprites +=
							((GroupOfText) list.get(index)).getNumCharacters();
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN
							* ((GroupOfText) list.get(index))
									.getNumCharacters();
					break;
				case SpriteBase.TYPE_09_TILE_MAP:
					int num =
							((SpriteTileMap) list.get(index))
									.getNumberOfImagesVisible();
					sprites += num;
					currentCapacity += Sprite.TEXTURE_QUAD_SPRITE_LEN * num;
					break;
				case SpriteBase.TYPE_08_TRIANGLES:
					sprites++;
					currentCapacity += Sprite.TRI_SPRITE_LEN; // 18 floats for a
					// triangle primitive
					break;
				case SpriteBase.TYPE_06_QUADS:
					sprites++;
					currentCapacity += Sprite.QUAD_SPRITE_LEN; // 24 floats for
																// a
					// quad primitive
					break;
				case SpriteBase.TYPE_04_LINE:
					sprites++;
					currentCapacity += Sprite.LINE_SPRITE_LEN; // 12 floats for
																// a
					// line primitive
					break;
				}
				currentSpriteIteration.add(list.get(index));
			}
		} // end for
		if (currentSpriteIteration.size() > 0) { // render any images remaining
			renderVBOData(currentType, sprites, textureId);
		}
	}
	/**
	 * Renders data from the vertex buffer object.
	 * @param type the type of images being rendered
	 * @param numSprites the number of sprites being rendered
	 * @param textureId the id of the texture used, if applicable
	 * @throws Exception if an error occurs
	 */
	private void renderVBOData(final int type,
			final int numSprites, final int textureId) throws Exception {
		if (debug) {
			System.out.println("**RENDER BATCH");
		}
		switch (type) {
		case SpriteBase.TYPE_00_BASIC_IMAGE:
		case SpriteBase.TYPE_01_GROUP_IMAGE:
		case SpriteBase.TYPE_03_GROUP_TEXT:
		case SpriteBase.TYPE_09_TILE_MAP:
		case SpriteBase.TYPE_10_PARTICLE:
			wrapData(type);
			renderTexturedQuadVertices(textureId,
					numSprites * GlobalConstants.INT_004);
			break;
		case SpriteBase.TYPE_08_TRIANGLES:
			wrapData(type);
			renderTriangleVertices(numSprites * GlobalConstants.INT_003);
			break;
		case SpriteBase.TYPE_06_QUADS:
			wrapData(type);
			renderQuadVertices(numSprites * GlobalConstants.INT_004);
			break;
		case SpriteBase.TYPE_02_GROUP_LINES:
		case SpriteBase.TYPE_04_LINE:
			wrapData(type);
			renderLineVertices(numSprites * 2);
			break;
		default:
			throw new Exception(
					"Invalid primitive type being rendered - " + type + "!");
		}
		if (debug) {
			System.out.println("**END RENDER BATCH");
		}
	}
	private void wrapData(final int type) throws Exception {
		if (debug) {
			System.out.println("Wrapping data for render");
		}
		// create room for the new data in the buffer
		if (vertexData.capacity() < currentCapacity) {
			// buffer is too small, allocate a new buffer
			vertexData = BufferUtils.newFloatBuffer(currentCapacity);
		} else {
			// buffer has enough capacity, clear old buffer
			vertexData.clear();
		}
		// create room for the new data in the buffer
		if (indexData.capacity() < currentCapacity) {
			// buffer is too small, allocate a new buffer
			indexData = BufferUtils.newShortBuffer(currentCapacity);
		} else {
			// buffer has enough capacity, clear old buffer
			indexData.clear();
		}
		float[] vertexArray = new float[currentCapacity];
		short[] indexArray = null;
		switch (type) {
		case SpriteBase.TYPE_00_BASIC_IMAGE:
		case SpriteBase.TYPE_01_GROUP_IMAGE:
		case SpriteBase.TYPE_03_GROUP_TEXT:
		case SpriteBase.TYPE_09_TILE_MAP:
		case SpriteBase.TYPE_10_PARTICLE:
			int len = currentCapacity / Sprite.TEXTURE_QUAD_SPRITE_LEN;
			len *= Render.INDICES_PER_QUAD;
			indexArray = new short[len];
			break;
		case SpriteBase.TYPE_06_QUADS:
			len = currentCapacity / Sprite.QUAD_SPRITE_LEN;
			len *= Render.INDICES_PER_QUAD;
			indexArray = new short[len];
			break;
		case SpriteBase.TYPE_02_GROUP_LINES:
		case SpriteBase.TYPE_04_LINE:
		case SpriteBase.TYPE_08_TRIANGLES:
			break;
		default:
			throw new Exception("Unrecognzed SpriteBase type - "
					+ type + "!");
		}
		int vertexArrayOffset = 0, indexArrayOffset = 0, vertexCount = 0;
		for (int i = 0; i < currentSpriteIteration.size(); i++) {
			switch (type) {
			case SpriteBase.TYPE_00_BASIC_IMAGE:
			case SpriteBase.TYPE_01_GROUP_IMAGE:
			case SpriteBase.TYPE_03_GROUP_TEXT:
			case SpriteBase.TYPE_06_QUADS:
			case SpriteBase.TYPE_09_TILE_MAP:
			case SpriteBase.TYPE_10_PARTICLE:
				SimplePoint pt = wrapIndexed(currentSpriteIteration.get(i),
						vertexArray, indexArray, vertexArrayOffset,
						indexArrayOffset, vertexCount);
				vertexArrayOffset = (int) pt.getX();
				indexArrayOffset = (int) pt.getY();
				if (type == SpriteBase.TYPE_03_GROUP_TEXT) {
					vertexCount +=
							((GroupOfText) currentSpriteIteration.get(i))
									.getNumCharacters()
									* Render.VERTICES_PER_QUAD;
				} else if (type == SpriteBase.TYPE_01_GROUP_IMAGE) {
					vertexCount +=
							((GroupOfImages) currentSpriteIteration.get(i))
									.getNumberOfImages()
									* Render.VERTICES_PER_QUAD;
				} else {
					vertexCount += Render.VERTICES_PER_QUAD;
				}
				pt = null;
				break;
			case SpriteBase.TYPE_02_GROUP_LINES:
			case SpriteBase.TYPE_04_LINE:
			case SpriteBase.TYPE_08_TRIANGLES:
				vertexArrayOffset = wrapNonIndexed(
						currentSpriteIteration.get(i), vertexArray,
						vertexArrayOffset);
				break;
			default:
				throw new Exception(
						"Invalid primitive type being wrapped - "
								+ type + "!");
			}
			if (debug) {
				System.out.print("Wrapped ");
				switch (type) {
				case SpriteBase.TYPE_00_BASIC_IMAGE:
					System.out.println("TYPE_00_BASIC_IMAGE ");
					break;
				case SpriteBase.TYPE_01_GROUP_IMAGE:
					System.out.println("TYPE_01_GROUP_IMAGE ");
					break;
				case SpriteBase.TYPE_02_GROUP_LINES:
					System.out.println("TYPE_02_GROUP_LINES ");
					break;
				case SpriteBase.TYPE_03_GROUP_TEXT:
					System.out.println("TYPE_03_GROUP_TEXT ");
					break;
				case SpriteBase.TYPE_04_LINE:
					System.out.println("TYPE_04_LINE ");
					break;
				case SpriteBase.TYPE_06_QUADS:
					System.out.println("TYPE_06_QUADS ");
					break;
				case SpriteBase.TYPE_08_TRIANGLES:
					System.out.println("TYPE_08_TRIANGLES ");
					break;
				case SpriteBase.TYPE_09_TILE_MAP:
					System.out.println("TYPE_09_TILE_MAP ");
					break;
				case SpriteBase.TYPE_10_PARTICLE:
					System.out.println("TYPE_10_PARTICLE ");
					break;
				default:
					throw new Exception(
							"Invalid primitive type being wrapped - "
									+ type + "!");
				}
				System.out.println("vertexCount::" + vertexCount);
				System.out.println("vertexArrayOffset::" + vertexArrayOffset);
				System.out.println("indexArrayOffset::" + indexArrayOffset);
			}
		}
		switch (type) {
		case SpriteBase.TYPE_00_BASIC_IMAGE:
		case SpriteBase.TYPE_01_GROUP_IMAGE:
		case SpriteBase.TYPE_03_GROUP_TEXT:
		case SpriteBase.TYPE_06_QUADS:
		case SpriteBase.TYPE_09_TILE_MAP:
		case SpriteBase.TYPE_10_PARTICLE:
			indexData.put(indexArray);
			indexData.flip();
			break;
		default:
			break;
		}
		vertexData.put(vertexArray);
		vertexArray = null;
		indexArray = null;
		vertexData.flip();
	}
	/**
	 * Wrap indexed sprite data in the vertex and index arrays.
	 * @param sprite the sprite
	 * @param destVertexArray the vertex array
	 * @param destIndexArray the index array
	 * @param vertexOffset the vertex array offset
	 * @param indexOffset the index array offset
	 * @return {@link SimplePoint}
	 * @throws Exception if an error occurs
	 */
	private SimplePoint wrapIndexed(final SpriteBase sprite,
			final float[] destVertexArray, final short[] destIndexArray,
			final int vertexOffset, final int indexOffset,
			final int vertexCount)
			throws Exception {
		if (debug) {
			System.out.println("**wrapIndexed(" + sprite + ", " 
		+ destVertexArray + ", " + destIndexArray + ", " + vertexOffset + ", " 
					+ indexOffset + ", " + vertexCount);
		}
		int totalIndices = 0;
		if (sprite.getFloatStoreNumber() > 0) {
			System.arraycopy(
					sprite.getFloatStore(),
					0,
					destVertexArray,
					vertexOffset,
					sprite.getFloatStoreNumber()
					);
		} else {
			switch (sprite.getType()) {
			case SpriteBase.TYPE_00_BASIC_IMAGE:
			case SpriteBase.TYPE_10_PARTICLE:
				Sprite.getInstance()
						.convertBasicSpriteToTexturedQuadDuringRender(
								sprite,
								destVertexArray,
								vertexOffset
						);
				break;
			case SpriteBase.TYPE_01_GROUP_IMAGE:
				Sprite.getInstance()
						.convertGroupOfImagesToTexturedQuadDuringRender(
								(GroupOfImages) sprite,
								destVertexArray,
								vertexOffset
						);
				break;
			case SpriteBase.TYPE_03_GROUP_TEXT:
				Sprite.getInstance()
						.convertGroupOfTextToTexturedQuadDuringRender(
								(GroupOfText) sprite,
								destVertexArray,
								vertexOffset
						);
				break;
			case SpriteBase.TYPE_06_QUADS:
				Sprite.getInstance().convertBasicSpriteToQuadDuringRender(
						sprite,
						destVertexArray,
						vertexOffset
						);
				break;
			case SpriteBase.TYPE_09_TILE_MAP:
				Sprite.getInstance().convertSpriteTileMapToTexturedQuad(
						(SpriteTileMap) sprite,
						destVertexArray,
						vertexOffset
						);
				break;
			default:
				throw new Exception("Unrecognized type " + sprite.getType());
			}
		}
		switch (sprite.getType()) {
		case SpriteBase.TYPE_00_BASIC_IMAGE:
		case SpriteBase.TYPE_10_PARTICLE:
		case SpriteBase.TYPE_06_QUADS:
			int count = vertexCount;
			System.arraycopy(
					new short[] { (short) count,
							(short) (count + 1),
							(short) (count + 2),
							(short) (count + 2),
							(short) (count + GlobalConstants.INT_003),
							(short) count },
					0,
					destIndexArray,
					indexOffset,
					Render.INDICES_PER_QUAD
					);
			count += Render.VERTICES_PER_QUAD;
			totalIndices += Render.INDICES_PER_QUAD;
			break;
		case SpriteBase.TYPE_01_GROUP_IMAGE:
			count = vertexCount;
			for (int i = 0; i < ((GroupOfImages) sprite).getNumberOfImages(); i++) {
				System.arraycopy(
						new short[] { (short) count,
								(short) (count + 1),
								(short) (count + 2),
								(short) (count + 2),
								(short) (count + GlobalConstants.INT_003),
								(short) count },
						0,
						destIndexArray,
						indexOffset + totalIndices,
						Render.INDICES_PER_QUAD
						);
				count += Render.VERTICES_PER_QUAD;
				totalIndices += Render.INDICES_PER_QUAD;
			}
			break;
		case SpriteBase.TYPE_03_GROUP_TEXT:
			count = vertexCount;
			for (int i = 0; i < ((GroupOfText) sprite).getNumCharacters(); i++) {
				System.arraycopy(
						new short[] { (short) count,
								(short) (count + 1),
								(short) (count + 2),
								(short) (count + 2),
								(short) (count + GlobalConstants.INT_003),
								(short) count },
						0,
						destIndexArray,
						indexOffset + totalIndices,
						Render.INDICES_PER_QUAD
						);
				count += Render.VERTICES_PER_QUAD;
				totalIndices += Render.INDICES_PER_QUAD;
			}
			break;
		case SpriteBase.TYPE_09_TILE_MAP:
			count = vertexCount;
			int numSprites =
					sprite.getFloatStoreNumber() / GlobalConstants.INT_005;
			for (int i = 0; i < numSprites; i++) {
				System.arraycopy(
						new short[] { (short) count,
								(short) (count + 1),
								(short) (count + 2),
								(short) (count + 2),
								(short) (count + GlobalConstants.INT_003),
								(short) count },
						0,
						destIndexArray,
						indexOffset + totalIndices,
						Render.INDICES_PER_QUAD
						);
				count += Render.VERTICES_PER_QUAD;
				totalIndices += Render.INDICES_PER_QUAD;
			}
			break;
		default:
			throw new Exception("Unrecognized type " + sprite.getType());
		}
		if (sprite.isDebug()) {
			System.out.println("vertices:texture:color");
			for (int i = 0; i < sprite.getFloatStoreNumber(); i++) {
				if (i % GlobalConstants.INT_005 == 0) {
					System.out.print(sprite.getFloatStore()[i] + ",");
				} else if (i % GlobalConstants.INT_005 == 1) {
					System.out.print(sprite.getFloatStore()[i] + "::");
				} else if (i % GlobalConstants.INT_005 == 2) {
					System.out.print(sprite.getFloatStore()[i] + ",");
				} else if (i % GlobalConstants.INT_005
				== GlobalConstants.INT_003) {
					System.out.print(sprite.getFloatStore()[i] + "::");
				} else {
					System.out.println(sprite.getFloatStore()[i]);
				}
			}
			System.out.println("indices");
			for (int i = indexOffset; i < indexOffset + totalIndices; i++) {
				System.out.print(destIndexArray[i] + ",");
			}
			System.out.println();
			System.out.println();

			System.out.println("indexOffset::" + indexOffset
					+ "\ntotalIndices::" + totalIndices);
		}
		return new SimplePoint(
				vertexOffset + sprite.getFloatStoreNumber(),
				indexOffset + totalIndices);
	}
	/**
	 * Wraps data for a non-index sprite into the vertex array.
	 * @param sprite the {@link SpriteBase} being processed
	 * @param destArray the vertex array where the data is being stored
	 * @param offset the offset where the next set of data should be indexed
	 * @return <code>int</code>
	 * @throws Exception if an error occurs
	 */
	private int wrapNonIndexed(final SpriteBase sprite,
			final float[] destArray,
			final int offset) throws Exception {
		if (sprite.getFloatStoreNumber() > 0) {
			System.arraycopy(
					sprite.getFloatStore(),
					0,
					destArray,
					offset,
					sprite.getFloatStoreNumber()
					);
		} else {
			switch (sprite.getType()) {
			case SpriteBase.TYPE_02_GROUP_LINES:
				Sprite.getInstance().convertGroupOfLinesToLinesDuringRender(
						(GroupOfLines) sprite,
						destArray,
						offset
						);
				break;
			case SpriteBase.TYPE_04_LINE:
				Sprite.getInstance().convertBasicSpriteToLineDuringRender(
						sprite,
						destArray,
						offset
						);
				break;
			case SpriteBase.TYPE_08_TRIANGLES:
				Sprite.getInstance().convertBasicSpriteToTriangleDuringRender(
						sprite,
						destArray,
						offset
						);
				break;
			default:
				throw new Exception(
						"Invalid type for wrapping non-indexed - "
								+ sprite.getType() + "!");
			}
		}
		return offset + sprite.getFloatStoreNumber();
	}
}
